package server

import (
	"github.com/go-xorm/xorm"

	"net/http"
	"os"

	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"

	"git.oschina.net/dddailing/drsys/backend/model"
	"github.com/dailing/levlog"
)

const (
	DB_NAME = "deepdrAPP"
)

/*
	Any Music record are in this table
	Including original and user records.
	Different Versions for the same song
	may exist in this table.
*/

type App struct {
	engine              *xorm.Engine
	mux                 *http.ServeMux
	mysqlHost           string
	dbName              string
	gradingTask         *Task
	qualityTask         *Task
	deepMarkerTask      *Task
	classification2Task *Task
	imageWaiteChannel   *sync.Map
	apiCounter          map[string]int
}

func NewApp(mysqlHost, dbName string) *App {
	return &App{
		mysqlHost: mysqlHost,
		dbName:    dbName,
	}
}

func (app *App) Init() *App {
	app.engine = model.NewDB(app.mysqlHost, app.dbName)
	app.mux = http.NewServeMux()
	app.SetRouter(app.mux)
	app.imageWaiteChannel = new(sync.Map)
	// check local storage
	if info, err := os.Stat(model.BinaryStorage); err != nil || info == nil {
		levlog.F(os.Mkdir(model.BinaryStorage, os.ModeDir|os.ModePerm))
	} else {
		if !info.IsDir() {
			levlog.Fatal(model.BinaryStorage, " is not a directory")
		}
	}
	// Make task obj
	app.gradingTask = NewTasks("grade", "redis:6379")
	app.qualityTask = NewTasks("quality", "redis:6379")
	app.deepMarkerTask = NewTasks("deepmarker", "redis:6379")
	app.classification2Task = NewTasks("new_grade", "redis:6379")
	if app.gradingTask == nil || app.qualityTask == nil {
		levlog.Fatal("Cannot set up task")
	}
	app.apiCounter = make(map[string]int)
	// add user
	levlog.E(model.AddUser("kashi2hosp", "$KASH2HOSP", "喀什第二人民医院"))
	levlog.E(model.AddUser("jonshanhosp", "$JINSHANHOSP", "上海市金山医院"))
	levlog.E(model.AddUser("anhuiyike1hosp", "$ANHUIYIKE1HOSP", "安徽医科大学第一附属医院"))
	levlog.E(model.AddUser("sxhosp", "$SXhosp", "浙江省绍兴市中医院"))
	levlog.E(model.AddUser("lingangHosp", "$LGhosp", "上海市第六人民医院临港东院"))
	levlog.E(model.AddUser("Liuyuan_Dongyuan", "#dongyuanshouquan", "上海市第六人民医院东院"))
	levlog.E(model.AddUser("shanghai8hosp", "$hospital8", "上海市第八人民医院"))
	levlog.E(model.AddUser("test", "test", "test"))
	levlog.E(model.AddManger("daichao", "$DAICHAO"))
	return app
}

func (app *App) Run() {
	server := &http.Server{Handler: app.mux}
	l, err := net.Listen("tcp4", ":12345")
	if err != nil {
		levlog.Fatal(err)
	}
	err = server.Serve(l)
	levlog.F(err)
	// levlog.Fatal(http.ListenAndServe(":12345", app.mux))
}

func (app *App) HandleToken(j *JsonType) (*JsonType, int) {
	user := j.GetReqObj().(*model.User)
	if err := user.Check(); err != nil {
		levlog.E(err)
		return ResponseError(403, err.Error())
	}
	token := user.GetToken()
	if token == "" {
		return ResponseError(500, "token generate error")
	}
	result := NewJson()
	result.Set("token", token)
	return ResponseJson(result)
}

func (app *App) HandleIndex(j *JsonType) (*JsonType, int) {
	return ResponseRaw("Hello from server\n")
}

func (app *App) HandleAddUser(j *JsonType) (*JsonType, int) {
	user := j.GetReqObj().(*model.User)
	err := model.AddUser(user.Name, user.RawPsw, user.Insitude)
	if err != nil {
		levlog.E(err)
		return ResponseError(500, err.Error())
	}
	return app.HandleToken(j)
}

func (app *App) HandleUpdateUserProfile(j *JsonType) (*JsonType, int) {
	var (
		token    *model.UserToken
		userInfo *model.User
		ok       bool
		err      error
	)
	if userInfo, ok = j.GetReqObj().(*model.User); !ok {
		return ResponseError(500, "this should not happen")
	}
	if token = j.GetReqToken(); token == nil {
		return ResponseError(500, err.Error())
	}
	userInfo.Id = token.UserID
	if err = userInfo.Update(); err != nil {
		levlog.E(err)
		return ResponseError(500, err.Error())
	}
	return NoResponse()
}

func (app *App) HandleGetUserProfile(j *JsonType) (*JsonType, int) {
	var (
		token    *model.UserToken
		userInfo *model.User
		err      error
	)
	userInfo = &model.User{}
	if token = j.GetReqToken(); token == nil {
		return ResponseError(500, err.Error())
	}
	userInfo.Id = token.UserID
	if err = userInfo.Get(); err != nil {
		levlog.E(err)
		return ResponseError(500, err.Error())
	}
	return ResponseAnyToJson(userInfo)
}

func (app *App) HandleInsertBinaryObject(j *JsonType) (*JsonType, int) {
	h := j.GetReqHeader()
	body := j.GetReqBody()
	bobj := model.NewBinaryObj(
		h.Get("name"),
		h.Get("ext"),
		h.Get("type"),
		h.Get("md_5_hash"),
		h.Get("Content-Type"),
		[]byte(body),
	)
	bobj.SetOwner(j.GetReqToken().UserID)
	if err := bobj.Save(); err != nil {
		levlog.E(err)
		return ResponseError(400, err.Error())
	}
	bobj.Url = "/binobject/get/" + bobj.UUID
	return ResponseAnyToJson(bobj)
}

func (app *App) HandleGetBinaryObject(j *JsonType) (*JsonType, int) {
	r := j.GetReqRaw()
	w := j.GetRespWriter()
	uuid := r.URL.String()
	binobj, err := model.GetBinaryObj(uuid)
	if err != nil {
		levlog.E(err)
		return ResponseError(500, err.Error())
	}
	(*w).Header().Set("name", binobj.Name)
	(*w).Header().Set("ext", binobj.Ext)
	(*w).Header().Set("type", binobj.Type)
	(*w).Header().Set("md_5_hash", binobj.Md5Hash)
	(*w).Header().Set("Content-Type", binobj.ContentType)
	return ResponseRaw(string(binobj.Payload))
}

func (app *App) HandleUploadPic(j *JsonType) (*JsonType, int) {
	r := j.GetReqRaw()
	w := *j.GetRespWriter()
	levlog.Debug("adding picture")
	if r.Method != http.MethodPost {
		levlog.Error("Method not allowd")
		w.WriteHeader(400)
		return ResponseError(400, "Method Not allowed")
	}
	// alowe 10 MB files stored in Memory
	levlog.E(r.ParseMultipartForm(10 * 1024 * 1024))
	file, handler, err := r.FormFile("file")
	levlog.Info("Readed file:", handler.Filename)
	levlog.Info("File size::", handler.Size)
	levlog.E(err)
	if err != nil {
		return ResponseError(500, err.Error())
	}
	if handler.Size > 25*1024*1024 {
		return ResponseError(400, "upload file too large")
	}
	payload, err := ioutil.ReadAll(file)
	levlog.E(err)
	if err != nil {
		return ResponseError(500, err.Error())
	}
	binfile := model.BinObject{
		Name:    handler.Filename,
		Payload: payload,
	}
	err = binfile.Save()
	levlog.E(err)
	if err != nil {
		return ResponseError(500, err.Error())
	}
	retjson := NewJson()
	retjson.Set("id", binfile.Id)
	retjson.Set("url", "/binobject/get/"+binfile.UUID)
	return ResponseJson(retjson)
}

func (app *App) Diagnose(j *JsonType) (*JsonType, int) {
	respGrade := app.gradingTask.IssueRaw(j.GetReqBody())
	respQuality := app.qualityTask.IssueRaw(j.GetReqBody())
	gradeRes := respGrade.GetJson()
	qualityResRaw := respQuality.GetRaw()
	qualityRes := make([][]int, 0)
	levlog.Debug(string(qualityResRaw))
	levlog.E(json.Unmarshal(qualityResRaw, &qualityRes))
	perimageResult := gradeRes.GetArrayOfObject("per_image_result")

	retval := NewJson()
	retval.Set("level", gradeRes.GetInt("level"))

	for i := 0; i < len(perimageResult); i++ {
		quality := qualityRes[i][0] // p_prob, a_prob, c_prob
		quality_position := qualityRes[i][1]
		quality_artifact := qualityRes[i][2]
		quality_clear := qualityRes[i][3]
		perimageResult[i].Remove("result")
		perimageResult[i].Set("quality", quality)
		perimageResult[i].Set("quality_position", quality_position)
		perimageResult[i].Set("quality_clearity", quality_clear)
		perimageResult[i].Set("quality_artifacts", quality_artifact)
		// perimageResult[i].Set("dsc", perimageResult[i].GetString("des"))
		// perimageResult[i].Remove("des")
	}
	retval.Set("richdata", perimageResult)
	//if retval.GetInt("level") >= 2 {
	//	retval.Set("result", "眼科就诊详查")
	//} else {
	//	retval.Set("result", "眼科随访")
	//}

	levlog.Debug(gradeRes)
	levlog.Debug(qualityRes)
	levlog.Debug(perimageResult)
	return ResponseJson(retval)
}

func (app *App) HandleAddPatient(j *JsonType) (*JsonType, int) {
	bodyRaw := j.GetReqBody()
	patient := &model.Patient{}
	token := j.GetReqToken()
	err := json.Unmarshal(bodyRaw, patient)
	levlog.E(err)
	if err != nil {
		return ResponseError(500, err.Error())
	}
	levlog.Debug(patient)
	err = patient.Insert()
	levlog.E(err)
	if err != nil {
		ResponseError(500, err.Error())
	}
	bodyjson := j.GetReqJson()
	pictures := bodyjson.GetArrayOfObject("pictures")
	levlog.Debug(pictures)
	for _, i := range pictures {
		id := int64(i.GetInt("id"))
		obj := model.PictureBelongsTo{
			PictureID: id,
			PatientID: patient.Id,
			UserID:    token.UserID,
		}
		// (id, patient.Id)
		obj.WhichEye = i.GetString("which_eye")
		err = obj.Insert()
		levlog.E(err)
		if err != nil {
			return ResponseError(500, err.Error())
		}
	}
	return ResponseAnyToJson(patient)
}

func (app *App) HandleGetSingleImageEng(j *JsonType) (*JsonType, int) {
	r := j.GetReqRaw()
	fields := strings.Split(r.URL.Path, "/")
	imgID, err := strconv.ParseInt(fields[len(fields)-1], 10, 64)
	levlog.Trace("Getting Image:", imgID)
	levlog.E(err)
	if err != nil {
		return ResponseError(403, err.Error())
	}

	muxInterface, _ := app.imageWaiteChannel.LoadOrStore(imgID, new(sync.Mutex))
	mux, _ := muxInterface.(*sync.Mutex)
	mux.Lock()
	defer func() {
		mux.Unlock()
		app.imageWaiteChannel.Delete(imgID)
	}()

	image := model.BinObject{
		Id: imgID,
	}
	err = image.Get()
	levlog.E(err)
	if err != nil {
		return ResponseError(500, err.Error())
	}
	patientImageMap := &model.PictureBelongsTo{
		PictureID: imgID,
	}
	err = patientImageMap.Get()
	levlog.E(err)
	if patientImageMap.Id == 0 {
		return ResponseError(500, "Image Record Not Found")
	}
	levlog.Debug("Picture_MAP", patientImageMap)
	if err != nil {
		return ResponseError(500, err.Error())
	}
	patientRec := model.Patient{
		Id: patientImageMap.PatientID,
	}
	err = patientRec.Get()
	levlog.E(err)
	if err != nil {
		return ResponseError(500, err.Error())
	}

	gradeReq := NewJson()
	binFile, err := model.GetBinaryObjByID(imgID)
	if err != nil {
		levlog.E(err)
		return ResponseError(500, err.Error())
	}
	payload := make([]string, 0)
	encodedPayload := base64.StdEncoding.EncodeToString(binFile.Payload)
	payload = append(payload, encodedPayload)
	gradeReq.Set("data", payload)
	gradeReq.Set("eng", 1)

	obj := NewJson()
	patient := NewJson()
	patient.Set("name", patientRec.Name)
	patient.Set("gender", patientRec.Gender)
	patient.Set("age", patientRec.Age)
	patient.Set("eye_sight", patientRec.Eye_sight)
	patient.Set("created_data", patientRec.CreatedAt.String())
	obj.Set("pathent", patient)

	var result *JsonType
	if len(patientImageMap.ResultRaw) > 0 {
		result = NewJsonFromString(patientImageMap.ResultRaw)
	} else {
		respGrade := app.gradingTask.IssueJson(gradeReq)
		respQuality := app.qualityTask.IssueJson(gradeReq)
		levlog.Debug("raw response : ", string(respGrade.GetRaw()))
		classificationResult := respGrade.GetJson()
		qualityArr := make([]float64, 0)
		err = json.Unmarshal(respQuality.GetRaw(), &qualityArr)
		levlog.E(err)
		perImageResult := classificationResult.GetArrayOfObject("per_image_result")
		if perImageResult == nil || len(perImageResult) < 1 {
			return ResponseError(500, "Failed to get result")
		}
		result = perImageResult[0]
		// result.Set("quality", qualityArr[0])
		result.Set("quality", 0)
		patientImageMap.ResultRaw = result.String()
		levlog.E(patientImageMap.Update())
	}
	// err = json.Unmarshal([]byte(patientImageMap.ResultRaw), result)
	if err == nil && result != nil {
		result.Set("ready", true)
	} else {
		result.Set("ready", false)
	}
	obj.Set("which_eye", patientImageMap.WhichEye)
	obj.Set("result", result)
	return ResponseJson(obj)
}

func (app *App) HandleGetSingleImage(j *JsonType) (*JsonType, int) {
	r := j.GetReqRaw()
	fields := strings.Split(r.URL.Path, "/")
	imgID, err := strconv.ParseInt(fields[len(fields)-1], 10, 64)
	levlog.Trace("Getting Image:", imgID)
	levlog.E(err)
	if err != nil {
		return ResponseError(403, err.Error())
	}

	muxInterface, _ := app.imageWaiteChannel.LoadOrStore(imgID, new(sync.Mutex))
	mux, _ := muxInterface.(*sync.Mutex)
	mux.Lock()
	defer func() {
		mux.Unlock()
		app.imageWaiteChannel.Delete(imgID)
	}()

	image := model.BinObject{
		Id: imgID,
	}
	err = image.Get()
	levlog.E(err)
	if err != nil {
		return ResponseError(500, err.Error())
	}
	patientImageMap := &model.PictureBelongsTo{
		PictureID: imgID,
	}
	err = patientImageMap.Get()
	levlog.E(err)
	if patientImageMap.Id == 0 {
		return ResponseError(500, "Image Record Not Found")
	}
	levlog.Debug("Picture_MAP", patientImageMap)
	if err != nil {
		return ResponseError(500, err.Error())
	}
	patientRec := model.Patient{
		Id: patientImageMap.PatientID,
	}
	err = patientRec.Get()
	levlog.E(err)
	if err != nil {
		return ResponseError(500, err.Error())
	}

	gradeReq := NewJson()
	binFile, err := model.GetBinaryObjByID(imgID)
	if err != nil {
		levlog.E(err)
		return ResponseError(500, err.Error())
	}
	payload := make([]string, 0)
	encodedPayload := base64.StdEncoding.EncodeToString(binFile.Payload)
	payload = append(payload, encodedPayload)
	gradeReq.Set("data", payload)

	obj := NewJson()
	patient := NewJson()
	patient.Set("name", patientRec.Name)
	patient.Set("gender", patientRec.Gender)
	patient.Set("age", patientRec.Age)
	patient.Set("eye_sight", patientRec.Eye_sight)
	patient.Set("created_data", patientRec.CreatedAt.String())
	obj.Set("pathent", patient)

	var result *JsonType
	if len(patientImageMap.ResultRaw) > 0 {
		result = NewJsonFromString(patientImageMap.ResultRaw)
	} else {
		respGrade := app.gradingTask.IssueJson(gradeReq)
		respQuality := app.qualityTask.IssueJson(gradeReq)
		classificationResult := respGrade.GetJson()
		levlog.Debug("getting grading result:",
			string(respGrade.GetRaw()))
		qualityArr := make([][]float64, 0)
		err = json.Unmarshal(respQuality.GetRaw(), &qualityArr)
		levlog.E(err)
		perImageResult := classificationResult.GetArrayOfObject("per_image_result")
		if perImageResult == nil || len(perImageResult) < 1 {
			return ResponseError(500, "Failed to get result")
		}
		result = perImageResult[0]
		// result.Set("quality", qualityArr[0][0])
		// result.Set("quality_position", qualityArr[0][1])
		// result.Set("quality_artifacts", qualityArr[0][2])
		// result.Set("quality_clearity", qualityArr[0][3])

		result.Set("quality", 0)
		result.Set("quality_position", 0)
		result.Set("quality_artifacts", 0)
		result.Set("quality_clearity", 0)

		patientImageMap.ResultRaw = result.String()
		levlog.E(patientImageMap.Update())
	}
	// err = json.Unmarshal([]byte(patientImageMap.ResultRaw), result)
	if err == nil && result != nil {
		result.Set("ready", true)
	} else {
		result.Set("ready", false)
	}
	obj.Set("which_eye", patientImageMap.WhichEye)
	obj.Set("result", result)
	return ResponseJson(obj)
}

type Imglist struct {
	Patient model.Patient          `xorm:"extends" json:"patient"`
	BinObj  model.BinObject        `xorm:"extends"`
	PicMap  model.PictureBelongsTo `xorm:"extends"`
	Result  *JsonType              `json:"result"`
}

func (Imglist) TableName() string {
	return "patient"
}

func (app *App) HandleGetImageList(j *JsonType) (*JsonType, int) {
	token := j.GetReqToken()
	users := make([]Imglist, 0)
	err := app.engine.
		Join("LEFT",
			"picture_belongs_to",
			"patient.id = picture_belongs_to.patient_i_d").
		Join("LEFT",
			"bin_object",
			"bin_object.id = picture_belongs_to.picture_i_d").
		Where("picture_belongs_to.user_i_d=?", token.UserID).
		Find(&users)
	for i := 0; i < len(users); i++ {
		users[i].BinObj.Url = "/binobject/get/" + users[i].BinObj.UUID
		users[i].Result = NewJsonFromString(users[i].PicMap.ResultRaw)
	}
	pay, _ := json.Marshal(users)
	levlog.E(err)
	levlog.Info(string(pay))
	return ResponseAnyToJson(users)
}

var deepmarker_kay_map = []string{
	"age",
	"male",
	"BMI",
	"waist",
	"hip",
	"heart_rate",
	"fasting_plasma_glucose",
	"fasting_insulin",
	"HbA1c",
	"diabetes_at_baseline",
	"triglyceride",
	"total_cholesterol",
	"HDL-c",
	"LDL-c",
	"SBP",
	"DBP",
	"hypertension",
	"hsCRP",
	"creatinine",
	"ever_smoker",
	"HTN_treatment",
}

func (app *App) HandleDeepMarker(j *JsonType) (*JsonType, int) {
	reqJson := j.GetReqJson()
	call_param := make([]float64, 0)
	for i := 0; i < len(deepmarker_kay_map); i++ {
		call_param = append(call_param, reqJson.GetFloat(deepmarker_kay_map[i]))
	}
	response := app.deepMarkerTask.IssueJson(call_param)
	levlog.Info("ISSUEING DEEPMARKER TASK:", call_param)
	raw := response.GetRaw()
	return ResponseRaw(string(raw))
}

func (app *App) HandleGetReport(j *JsonType) (*JsonType, int) {
	r := j.GetReqRaw()
	users := make([]Imglist, 0)
	fields := strings.Split(r.URL.Path, "/")
	idstr := fields[len(fields)-1]
	id, err := strconv.ParseInt(idstr, 10, 64)
	levlog.E(err)
	err = app.engine.
		Join("LEFT",
			"picture_belongs_to",
			"patient.id = picture_belongs_to.patient_i_d").
		Join("LEFT",
			"bin_object",
			"bin_object.id = picture_belongs_to.picture_i_d").
		Where("patient.id=?", id).
		Find(&users)
	levlog.E(err)
	pictures := make([]*JsonType, 0)
	for _, rec := range users {
		pict := NewJson()
		pict.Set("url", "/binobject/get/"+rec.BinObj.UUID)
		pict.Set("id", rec.BinObj.Id)
		pict.Set("which_eye", rec.PicMap.WhichEye)
		pict.Set("result", NewJsonFromString(rec.PicMap.ResultRaw))
		pictures = append(pictures, pict)
	}
	// result.Set("pictures", pictures)
	// users[0].Patient.Pics = pictures
	result := NewJson()
	result.Set("patient", users[0].Patient)
	result.Set("pictures", pictures)
	return ResponseAnyToJson(result)
}

func (app *App) HandleGetUI(j *JsonType) (*JsonType, int) {
	token := j.GetReqToken()
	ui := model.UiEle{
		UserId: token.UserID,
	}
	err := ui.Get()
	if err != nil {
		levlog.E(err)
		return ResponseError(500, err.Error())
	}
	return ResponseAnyToJson(ui)
}

func (app *App) HandleGetQuality(j *JsonType) (*JsonType, int) {
	resp := app.qualityTask.IssueRaw(j.GetReqBody())
	qualityArr := make([][]float64, 0)
	err := json.Unmarshal(resp.GetRaw(), &qualityArr)
	levlog.E(err)
	result := NewJson()
	result.Set("quality", qualityArr[0][0])
	result.Set("quality_position", qualityArr[0][1])
	result.Set("quality_artifacts", qualityArr[0][2])
	result.Set("quality_clearity", qualityArr[0][3])
	return ResponseJson(result)
}

func (app *App) HandleGetClassification2(j *JsonType) (*JsonType, int) {
	resp := app.classification2Task.IssueRaw(j.GetReqBody())
	resp_raw := resp.GetRaw()
	return ResponseRaw(string(resp_raw))
}

func (app *App) HandleLimitTest(j *JsonType) (*JsonType, int) {
	req := j.GetReqRaw()
	url := fmt.Sprintf("limit_%d_%s", time.Now().YearDay(), req.URL.String())
	return ResponseRaw(fmt.Sprintf("Visit Successful! Number:%d", app.apiCounter[url]))
}

func (app *App) LimitAPI(limit int) func(j *JsonType) (*JsonType, int) {
	return func(j *JsonType) (*JsonType, int) {
		req := j.GetReqRaw()
		url := fmt.Sprintf("limit_%d_%s", time.Now().YearDay(), req.URL.String())
		levlog.Info("URL KEY", url)
		if app.apiCounter[url] >= limit {
			return ResponseError(
				400,
				fmt.Sprintf("You have visited this api for %d times today. Usage of this api is restricted today.\n", app.apiCounter[url]))
		}
		app.apiCounter[url] += 1
		return ResponseSucc()
	}
}

func (app *App) LimitUserAccessLevel(level int64) func(j *JsonType) (*JsonType, int) {
	return func(j *JsonType) (*JsonType, int) {
		token := j.GetReqToken()
		if token.AccessLevel < level {
			levlog.Warning(
				"Access Denied, Access level not high enough!, Username:",
				token.UserName, " UserID:", token.UserID)
			return ResponseError(400, "Access Denied")
		}
		return ResponseSucc()
	}
}

func (app *App) CallTasks(TaskName string) func(j *JsonType) (*JsonType, int) {
	task := NewTasks(TaskName, "")
	return func(j *JsonType) (*JsonType, int) {
		levlog.Info("Calling task:", TaskName)
		resp := task.IssueJson(j.GetReqJson())
		jsonResp := resp.GetJson()
		return ResponseJson(jsonResp)
	}
}

func (app *App) CallTasksWithRawResponse(TaskName string) func(j *JsonType) (*JsonType, int) {
	task := NewTasks(TaskName, "")
	return func(j *JsonType) (*JsonType, int) {
		levlog.Info("Calling task:", TaskName)
		resp := task.IssueJson(j.GetReqJson())
		jsonResp := resp.GetRaw()
		return ResponseRaw(string(jsonResp))
	}
}
